//
//  Disassembler.hpp
//  Clock Signal
//
//  Created by Thomas Harte on 26/01/2021.
//  Copyright © 2021 Thomas Harte. All rights reserved.
//

#pragma once

#include "Numeric/Sizes.hpp"

#include <list>
#include <map>
#include <set>

namespace InstructionSet {

template <
	/// Indicates the Parser for this platform.
	template<typename, bool> class ParserType,
	/// Indicates the greatest value the program counter might take.
	uint64_t max_address,
	/// Provides the type of Instruction to expect.
	typename InstructionType,
	/// Provides the storage size used for memory.
	typename MemoryWord,
	/// Provides the addressing range of memory.
	typename AddressType
> class Disassembler {
public:
	using ProgramCounterType = min_int_for_value_t<max_address>;

	/*!
		Adds the result of disassembling @c memory which is @c length @c MemoryWords long from @c start_address
		to the current net total of instructions and recorded memory accesses.
	*/
	void disassemble(
		const MemoryWord *const memory,
		const ProgramCounterType location,
		const ProgramCounterType length,
		const ProgramCounterType start_address
	) {
		// TODO: possibly, move some of this stuff to instruction-set specific disassemblers, analogous to
		// the Executor's ownership of the Parser. That would allow handling of stateful parsing.
		ParserType<decltype(*this), true> parser;
		pending_entry_points_.push_back(start_address);
		entry_points_.insert(start_address);

		while(!pending_entry_points_.empty()) {
			const auto next_entry_point = pending_entry_points_.front();
			pending_entry_points_.pop_front();

			if(next_entry_point >= location) {
				parser.parse(*this, memory - location, next_entry_point & max_address, length + location);
			}
		}
	}

	const std::map<ProgramCounterType, InstructionType> &instructions() const {
		return instructions_;
	}

	const std::set<ProgramCounterType> &entry_points() const {
		return entry_points_;
	}

	void announce_overflow(ProgramCounterType) {}
	void announce_instruction(const ProgramCounterType address, const InstructionType instruction) {
		instructions_[address] = instruction;
	}
	void add_entry(const ProgramCounterType address) {
		if(entry_points_.find(address) == entry_points_.end()) {
			pending_entry_points_.push_back(address);
			entry_points_.insert(address);
		}
	}
	void add_access(const AddressType address, const AccessType access_type) {
		// TODO.
		(void)address;
		(void)access_type;
	}

private:
	std::map<ProgramCounterType, InstructionType> instructions_;
	std::set<ProgramCounterType> entry_points_;

	std::list<ProgramCounterType> pending_entry_points_;
};

}
